package cn.wowjoy.office.data.remote;

import android.text.TextUtils;

import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;

/**
 * Created by Sherily on 2017/11/28.
 * Description:
 */

public class BRUploadThread extends Thread {
    private final long MAX_PICE_SIZE = 10 * 1024 * 1024; //每片大小
    private final int MAX_TIME_OUT = 60 * 1000;     //超时设置
    private final int MAX_BUFF_SIZE = 4 * 1024;    //每次读的buff大小，MAX_PICE_SIZE / MAX_BUFF_SIZE == 进度监听调用次数
    private Builder mBuilder;
    private long mCompleteLenth = 0;                //已完成的长度
    private long mAllLenth = 0;                     //总长度
    private long mStartTime = 0;                    //起始时间
    private boolean isRun = true;                   //是否在上传

    /***
     * 同一个文件标识符
     * @return
     */
    public String getSeesionId() {
        return mBuilder.mSeesionId;
    }

    /**
     * 已完成长度(当次已完成长度，不是总完成长度)
     * @return
     */
    public long getCompleteLength() {
        return mCompleteLenth;
    }

    /***
     * 被上传的文件
     * @return
     */
    public File getFile() {
        return mBuilder.mFile;
    }

    /***
     * 退出上传，会在当前pice上传完成后停止上传下一pice
     */
    public void exit() {
        isRun = false;
        if(mBuilder.mOnStopListenner != null) {
            mBuilder.mOnStopListenner.onChanged(Listenner.STATUS_STOP,mBuilder.mUrl);
        }
        //LogUtil.i("completeLength=" + mCompleteLenth);
    }

    /***
     * BRUploadThread无法实例化，需要使用Builder创建
     */
    public static class Builder {
        private Map<String, String> mHeaders;    //请求头
        private String mUrl;                     //接口地址
        private File mFile;                      //要上传的文件
        private Map<String, String> mParams;     //url参数，是跟在url后的参数，不是放在body中
        private Listenner mSuccessListenner;     //成功回调
        private Listenner mErrorListnner;        //错误回调
        private Listenner mSpeedListenner;       //速度回调
        private Listenner mLengthChangeListnner; //进度回调
        private Listenner mOnStopListenner;      //停止回调
        private String mSeesionId;               //同一文件标识
        private long mStartOffset = 0;           //断点上传起始位置

        /***
         * 如果需要断点续传，需要设置
         * @param startOffset
         * @param sessionId
         * @return
         */
        public Builder setContinueBreakPointParams(long startOffset, String sessionId) {
            mStartOffset = startOffset;
            mSeesionId = sessionId;
            return this;
        }

        public Builder setOnStopListenner(Listenner l) {
            mOnStopListenner = l;
            return this;
        }

        public Builder(String url) {
            mUrl = url;
        }

        public Builder() {
        }

        public Builder setUrl(String url) {
            mUrl = url;
            return this;
        }

        /***
         * 设置额外的请求头
         * @param headers
         * @return
         */
        public Builder setHeaders(Map<String, String> headers) {
            mHeaders = headers;
            return this;
        }

        public synchronized Builder addHeader(String k, String v) {
            if (mHeaders == null) {
                mHeaders = new HashMap<>();
            }
            mHeaders.put(k, v);
            return this;
        }

        public synchronized Builder setFile(File file) {
            mFile = file;
            return this;
        }

        public Builder setSuccessListenner(Listenner l) {
            mSuccessListenner = l;
            return this;
        }

        public Builder setErrorListenner(Listenner l) {
            mErrorListnner = l;
            return this;
        }

        public Builder setSpeedListenner(Listenner l) {
            mSpeedListenner = l;
            return this;
        }

        public Builder setLengthChangeListenner(Listenner l) {
            mLengthChangeListnner = l;
            return this;
        }

        public BRUploadThread build() {
            BRUploadThread uploader = new BRUploadThread();
            //首次传输，指定标识
            if (mSeesionId == null) {
                mSeesionId = getSessionID();
            }
            uploader.mBuilder = this;
            return uploader;
        }

        public Builder setUrlParams(Map<String, String> params) {
            mParams = params;
            return this;
        }

        public synchronized Builder addUrlParam(String k, String v) {
            if (mParams == null) {
                mParams = new HashMap<>();
            }
            mParams.put(k, v);
            return this;
        }
    }

    /***
     * <p></p>code == STATUS_SUCCESS</p>
     * <p>value[0] == response; 最后完成返回的内容</p>
     *
     * <p>code == STATUS_ERROR</p>
     * <p>value[0] == e.getMessage(); 错误信息</p>
     *
     * <p>code == STATUS_LENGTH_CHANGE</p>
     * <p>value[0] == allLenth; 总长度</p>
     * <p>value[1] == completeLenth;</p>
     *
     * <p>code == STATUS_SPEED_CHANGE</p>
     * <p>value[0] == completeLenth; 完成长度</p>
     * <p>value[1] == durTime; 耗时</p>
     * <p>value[2] == speedStr;  速度字符串</p>
     *
     * <p>code == STATUS_STOP</p>
     * <p>value[0] == filePath; 停止上传的文件路径</p>
     * <p>
     */
    public static interface Listenner {
        int STATUS_SUCCESS = 0X001;
        int STATUS_ERROR = 0X002;
        int STATUS_LENGTH_CHANGE = 0X003;
        int STATUS_SPEED_CHANGE = 0X004;
        int STATUS_STOP = 0X005;

        void onChanged(int code, String... value);
    }

    protected static String getSessionID() {
        Random ran = new Random(System.currentTimeMillis());
        StringBuffer strb = new StringBuffer();
        int num1 = 0;
        for (int i = 0; i < 64 / 8; i++) {// 这里是产生9位的64/8=8次,
            while (true) {
                num1 = ran.nextInt(99999999);
                if (num1 > 10000000) {
                    strb.append(num1);
                    break;
                }
            }
        }
//        String str = MdValue.getEncode("MD5", strb.toString());
        return "";
    }

    /***
     * 上传一片文件
     * @param actionUrl 接口地址
     * @param filelName 文件名
     * @param sessionId 文件标识
     * @param file      文件
     * @param start     起始位置
     * @param end       结束位置
     * @return
     * @throws Exception
     */
    private String uploadPiceFile(String actionUrl, String filelName, String sessionId, RandomAccessFile file, long start, long end) throws Exception {
        URL url = new URL(actionUrl);

        HttpURLConnection hc = (HttpURLConnection) url.openConnection();
        hc.setDoOutput(true);
        hc.setDoInput(true);
        hc.setConnectTimeout(MAX_TIME_OUT);
        hc.setUseCaches(false);
        //hc.setFixedLengthStreamingMode(MAX_BUFF_SIZE);

        hc.setRequestProperty("Content-Type", "application/octet-stream");
        hc.setRequestProperty("Session-ID", sessionId);
        hc.setRequestProperty("Content-Range", "bytes " + start + "-" + (end - 1) + "/" + file.length());
        hc.setRequestProperty("Content-Disposition", "attachment; filename=" + filelName);
        hc.setRequestProperty("Connection", "Keep-Alive");
        if (mBuilder.mHeaders != null) {
            for (String k : mBuilder.mHeaders.keySet()) {
                hc.setRequestProperty(k, mBuilder.mHeaders.get(k));
            }
        }
        OutputStream os = hc.getOutputStream();
        //写一片文件，由于HttpUrlConnection 缓存原因，写入的每片大小不能过大，否则会占用系统内存
        boolean ret = writeBuff(file, start, end, os);
        if(!ret) {
            return null;
        }
        InputStream is = hc.getInputStream();
        int len = 0;
        byte[] buf = new byte[MAX_BUFF_SIZE];
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        while ((len = is.read(buf)) != -1) {
            baos.write(buf, 0, len);
        }
        String str = baos.toString();
        //如果返回错误吗
        if (hc.getResponseCode() > 299) {
            mBuilder.mErrorListnner.onChanged(Listenner.STATUS_ERROR, str);
            return null;
        }
        return str;
    }

    /***
     * 上传文件
     * @param postUrl 接口地址
     * @param file    文件
     * @param sessionId  文件标识
     * @throws Exception
     */
    private void uploadFile(String postUrl, File file, String sessionId) throws Exception {
        RandomAccessFile rFile = new RandomAccessFile(file, "r"); //创建一个可随机读取的文件
        mAllLenth = rFile.length();
        int count = 0;
        long start = 0;
        long end = 0;
        if (mBuilder.mStartOffset != 0) {
            rFile.skipBytes((int) mBuilder.mStartOffset);//断点续传设置起始传输位置
        }
        long unCompleteLength = rFile.length() - rFile.getFilePointer(); //未完成长度

        //计算需要传输的片数
        count = (int) (unCompleteLength / MAX_PICE_SIZE);
        if (unCompleteLength % MAX_PICE_SIZE != 0) {
            count += 1;
        }
        //上传每一片
        for (int i = 0; i < count && isRun; i++) {
            //每次起始位置为当前文件指针位置
            start = rFile.getFilePointer();
            if (rFile.length() - start > MAX_PICE_SIZE) {
                end = start + MAX_PICE_SIZE;
            } else {
                end = rFile.length();
            }
            String ret = uploadPiceFile(postUrl, file.getName(), sessionId, rFile, start, end);
            //成功上传一片文件回调进度监听
            if (!TextUtils.isEmpty(ret) && mBuilder.mLengthChangeListnner != null) {
                mBuilder.mLengthChangeListnner.onChanged(Listenner.STATUS_LENGTH_CHANGE, mAllLenth + "", (mBuilder.mStartOffset + mCompleteLenth) + "");
            }
            //上传成功回调成功监听
            if (isSuccessful(ret)) {
                if (mBuilder.mSuccessListenner != null) {
                    mBuilder.mSuccessListenner.onChanged(Listenner.STATUS_SUCCESS, ret);
                }
                if (mBuilder.mFile != null) {
                    mBuilder.mFile = null;
                }
                break;
            }
        }
    }

    /***
     * 往输出流写入一片文件
     * @param rFile  要上传的文件
     * @param start  起始文件指针位置
     * @param end    结束位置
     * @param os     要写入到的输出流
     * @return       如果成功发送则返回true,否则返回false
     * @throws IOException
     */
    private boolean writeBuff(RandomAccessFile rFile, long start, long end, OutputStream os) throws IOException {
        byte[] buf = new byte[MAX_BUFF_SIZE];
        int len = 0;
        int allLen = 0;
        BufferedOutputStream bos = new BufferedOutputStream(os);
        while ((len = rFile.read(buf)) != -1) {
            bos.write(buf, 0, len);
            bos.flush();
            allLen += len;
            mCompleteLenth += len;
            changeSpeed();
            if (allLen >= end - start) {
                return true;
            }
        }
        return false;
    }

    /***
     * 传输速度变化监听
     */
    private void changeSpeed() {
        if (mBuilder.mSpeedListenner != null) {
            long time = System.currentTimeMillis() - mStartTime;
            time = time / 1000;
            String speed;
            if (time == 0) {
//                speed = FileSizeUtil.FormatFileSize(0);
            } else {
//                speed = FileSizeUtil.FormatFileSize(mCompleteLenth / time);
            }
            mBuilder.mSpeedListenner.onChanged(Listenner.STATUS_SPEED_CHANGE, mCompleteLenth + "", (mBuilder.mStartOffset + mCompleteLenth) + "",mStartTime + "", /*speed + */"/S");
        }
    }

    /***
     * 如果是断点续传，则必须同时指定 mBuilder.mStartOffset 和 mBuilder.mSeesionId
     * @throws Exception
     */
    private void checkUploadEnvirment() throws Exception {
        if (mBuilder.mStartOffset != 0 && TextUtils.isEmpty(mBuilder.mSeesionId)) {
            throw new Exception("if you want to upload from breakpoint,you should set the mStartOffset and mSeesionId!");
        }

//        if (!TextUtils.isEmpty(mBuilder.mSeesionId) && mBuilder.mStartOffset == 0) {
//            throw new Exception("if you want to upload from breakpoint,you should set the mStartOffset and mSeesionId!");
//        }
    }

    @Override
    public void run() {
        super.run();
        if (mStartTime == 0) {
            mStartTime = System.currentTimeMillis();
        }
        String postUrl = getUrl(mBuilder.mUrl, mBuilder.mParams);
        postUrl = URLEncoder.encode(postUrl);
        try {
            checkUploadEnvirment();
            uploadFile(postUrl, mBuilder.mFile, mBuilder.mSeesionId);
        } catch (Exception e) {
            e.printStackTrace();
            if (mBuilder.mErrorListnner != null) {
                mBuilder.mErrorListnner.onChanged(Listenner.STATUS_ERROR, e.getMessage());
            }
        }
    }

    /***
     * 将urlParams添加到url后
     * @param baseUrl
     * @param params
     * @return
     */
    private String getUrl(String baseUrl, Map<String, String> params) {
        if (params == null || params.isEmpty()) return baseUrl;
        String param = "?";
        for (String k : params.keySet()) {
            param += k + "=" + params.get(k) + "&";
        }
        param = param.substring(0, param.length() - 1);
        return baseUrl + param;
    }

    /***
     * 判断上传完成的条件，可以重写，自定义的条件
     *
     * @param response
     * @return
     */
    protected boolean isSuccessful(String response) {
        if (response == null) return false;
        return response.contains("code");
    }
}
